<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link href="common/bulma.min.css" rel="stylesheet">
        <script defer src="common/alpinejs@3.12.0.min.js"></script>
    </head>
    <body>
		<section class="section">
			<div class="container" x-data="makeGuessNumber()">
				<h1 class="title is-1">猜数字</h1>
				<div class="box" x-show="state === 'start'">
					<div class="field">
						<label class="label">猜数字的最大范围（10~255）：</label>
						<input class="input" x-model="maxStr" type="text" required minlength="2" maxlength="3" size="4">
						<p class="help is-success" x-show="validMax">请在心中想一个数字，范围1~<span x-text="max"></span></p>
						<p class="help is-danger" x-show="!validMax">请输入10~255之间的整数</p>
					</div>
					<div class="field">
						<button class="button is-primary" @click="start()">开始猜数字</button>
					</div>
				</div>
				<div class="box" x-show="state === 'ask'">
					<p class="label">下表中是否有你心中所想的数字？<span x-text="`（${nth}/${tableCount}）`"></span></p>
					<table class="table">
						<template x-for="row in table">
							<tr>
								<template x-for="num in row">
									<td x-text="num"></td>
								</template>
							</tr>
						</template>
					</table>
					<div class="field is-grouped">
						<p class="control">
							<button class="button is-primary" @click="respond(true)">是</button>
						</p>
						<p class="control">
							<button class="button" @click="respond(false)">否</button>
						</p>
					</div>
				</div>
				<div class="box" x-show="state === 'result'">
					<p class="mb-3 has-text-success">你心中所想的数字是<span x-text="result"></span></p>
					<button class="button is-primary" @click="reset()">重新开始</button>
				</div>
				<div class="box" x-show="state === 'error'">
					<p class="mb-3 has-text-danger">前面的回答中存在错误！</p>
					<div class="field">
						<button class="button is-primary" @click="reset()">重新开始</button>
					</div>
				</div>
			</div>
		</section>
    </body>
    <script lang="text/javascript">
        (function(window) {
            window.makeGuessNumber = makeGuessNumber;

            /// model
            function isValidMax(max) {
                return Number.isInteger(max) && max >= 10 && max <= 255;
            }

            function getTableCount(max) {
                let count = 0;
                while (max > 0) {
                    max >>= 1;
                    count++;
                }
                return count;
            }

            // nth: 1 ~ getTableCount(max)
            function getNumbers(max, nth) {
                let mask = 1 << (nth - 1);
                let result = [];
                for (let i = 1; i <= max; i++) {
                    if ((i & mask) !== 0) {
                        result.push(i);
                    }
                }
                return result;
            }

            function getTable(nums) {
                const rowLen = 10;
                let table = [];
                while (nums.length > rowLen) {
                    table.push(nums.slice(0, rowLen));
                    nums = nums.slice(rowLen);
                }

                while (nums.length < rowLen) {
                    nums.push("")
                }
                table.push(nums);
                return table;
            }

            function getResult(resps) {
                let result = 0;
                let mask = 1;
                for (const resp of resps) {
                    if (resp) {
                        result |= mask;
                    }
                    mask <<= 1;
                }
                return result;
            }

            /// view model
            function makeGuessNumber() {
                return {
                    // view -> model
                    maxStr: "100",
                    responses: [],
                    // model -> view
                    state: 'start', // or 'ask', 'result', 'error'
                    nth: 1,
                    // dep on maxStr
                    validMax: true,
                    max: 100,
                    // dep on max
                    tableCount: getTableCount(100),
                    // dep on max and nth
                    table: [],
                    // dep on responses
                    result: 1,

                    init: function() {
                        Alpine.effect(() => {
                            const max = Number.parseFloat(this.maxStr);
                            this.validMax = isValidMax(max);
                            this.max = max;
                        });

                        Alpine.effect(() => {
                            this.tableCount = getTableCount(this.max);
                        })

                        Alpine.effect(() => {
                            this.table = getTable(getNumbers(this.max, this.nth));
                        })
                    },
                    reset: function() {
                        this.state = 'start';
                    },
                    start: function() {
                        this.nth = 1;
                        this.responses = [];
                        this.state = 'ask';
                    },
                    respond: function(isInTable) {
                        this.responses.push(isInTable);
                        if (this.nth < this.tableCount) {
                            this.nth++;
                        } else {
                            this.result = getResult(this.responses);
                            if (this.result < 1 || this.result > this.max) {
                                this.state = 'error';
                            } else {
                                this.state = 'result';
                            }
                        }
                    }
                }
            }
        })(window)
    </script>
</html>
